// Minimal Windows Screen Saver
//
// This particular screen saver takes a snapshot of the desktop upon startup,
// keeps the snapshot as a back-buffer, and draws blobs on it. There's a
// configuration option to change the size of the blobs.
//
// The basic things to change are marked TODO. They are: load/save the global
// settings, create an animation for the saver, make its Configuration dialog,
// and set its name in the Stringtable resource.
// But the source code is very short and clear, so the programmer should feel
// confident to modify all of it.
//

#pragma warning( disable: 4127 4800 4702 )
#include <string>
#include <vector>
#include <math.h>
#include <windows.h>
#include <commctrl.h>
#include <regstr.h>
#include <stdlib.h>
#include <tchar.h>
typedef std::basic_string<TCHAR> tstring;
using namespace std;



//
// TODO: set the saver's name and URL in the STRINGTABLE resource.
//



// If you want debugging output, set this to "OutputDebugString" or a filename. The second line controls the saver's "friendliness"
const tstring DebugFile=_T("OutputDebugString");
const bool SCRDEBUG = (DebugFile!=_T(""));
//
// These global variables are loaded at the start of WinMain
BOOL  MuteSound;
DWORD MouseThreshold;  // In pixels
DWORD PasswordDelay;   // In seconds. Doesn't apply to NT/XP/Win2k.
// also these, which are used by the General properties dialog
DWORD PasswordDelayIndex;  // 0=seconds, 1=minutes. Purely a visual thing.
DWORD MouseThresholdIndex; // 0=high, 1=normal, 2=low, 3=ignore. Purely visual
TCHAR Corners[5];          // "-YN-" or something similar
BOOL  HotServices;         // whether they're present or not
// and these are created when the dialog/saver starts
POINT InitCursorPos;
DWORD InitTime;        // in ms
bool  IsDialogActive;
bool  ReallyClose;     // for NT, so we know if a WM_CLOSE came from us or it.
// Some other minor global variables and prototypes
HINSTANCE hInstance=0;
HICON hiconsm=0,hiconbg=0;
HBITMAP hbmmonitor=0;  // bitmap for the monitor class
tstring SaverName;     // this is retrieved at the start of WinMain from String Resource 1
vector<RECT> monitors; // the rectangles of each monitor (smSaver) or of the preview window (smPreview)
struct TSaverWindow;
const UINT SCRM_GETMONITORAREA=WM_APP; // gets the client rectangle. 0=all, 1=topleft, 2=topright, 3=botright, 4=botleft corner
inline void Debug(const tstring s);
tstring GetLastErrorString();
void SetDlgItemUrl(HWND hdlg,int id,const TCHAR *url);
void   RegSave(const tstring name,int val);
void   RegSave(const tstring name,bool val);
void   RegSave(const tstring name,tstring val);
int    RegLoad(const tstring name,int def);
bool   RegLoad(const tstring name,bool def);
tstring RegLoad(const tstring name,tstring def);

//
// IMPORTANT GLOBAL VARIABLES:
enum TScrMode {smNone,smConfig,smPassword,smPreview,smSaver,smInstall,smUninstall} ScrMode=smNone;
vector<TSaverWindow*> SaverWindow;   // the saver windows, one per monitor. In preview mode there's just one.

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------


// These are the saver's global settings
int refcount=0;      // we use a global refcount so that settings aren't loaded more than necessary
bool BigBlobs=false; // this is our setting that we're storing in the registry

void CommonInit()
{ refcount++; if (refcount!=1) return;
  //
  // TODO: saver initialization - load settings from registry &c.
  // Some things are naturally global, and shared between configuration
  // dialog and saver-preview, or between multiple monitors when running full-screen.
  //
  BigBlobs=RegLoad(_T("bigblobs"),true);
}





// TSaverWindow: one is created for each saver window (be it preview, or the
// preview in the config dialog, or one for each monitor when running full-screen)
// For things like back-buffer that are inherently related to a particular window,
// I store them here. (But things that are shared between all saver windows on a multi
// monitor system, I leave as global instead).
//
struct TSaverWindow
{ HWND hwnd; int id;  // id=-1 for a preview, or 0..n for full-screen on the specified monitor
  HANDLE hbmBuffer;   // this is a back-buffer used by the saver
  int bw,bh;          // dimensions of the back-buffer
  //
  TSaverWindow(HWND _hwnd,int _id) : hwnd(_hwnd),id(_id)
  { CommonInit(); SetTimer(hwnd,1,100,NULL);
    // In this example we take a snapshot of the screen at startup. This trick doesn't
    // really work under NT/Win2k/XP, where savers are run on their own private desktops.
    // If we're a preview window, we snapshot the primary desktop. If we're a full-screen
    // saver, we snapshot whatever monitor we were running on.
    RECT rc; GetClientRect(hwnd,&rc); bw=rc.right; bh=rc.bottom;
    if (id==-1) {rc.left=0; rc.top=0; rc.right=GetSystemMetrics(SM_CXSCREEN); rc.bottom=GetSystemMetrics(SM_CYSCREEN);}
    else GetWindowRect(hwnd,&rc);
    //
    HDC sdc=GetDC(0), bufdc=CreateCompatibleDC(sdc);
    hbmBuffer=CreateCompatibleBitmap(sdc,bw,bh); SelectObject(bufdc,hbmBuffer);
    SetStretchBltMode(bufdc,COLORONCOLOR);
    StretchBlt(bufdc,0,0,bw,bh,sdc,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,SRCCOPY);
    DeleteDC(bufdc);
    ReleaseDC(0,sdc);
  }
  ~TSaverWindow()
  { KillTimer(hwnd,1);
    if (hbmBuffer!=0) DeleteObject(hbmBuffer); hbmBuffer=0;
  }
  void OnTimer()
  { HDC sdc=GetDC(0), bufdc=CreateCompatibleDC(sdc); ReleaseDC(0,sdc);
    SelectObject(bufdc,hbmBuffer);
    //
    // TODO: make the saver animate!
    //
    int col=rand()%20;
    int divisor=(BigBlobs?2:8);
    int cx=rand()%bw, cy=rand()%bh, w=rand()%(bw/divisor), h=rand()%(bh/divisor);    
    SelectObject(bufdc,GetSysColorBrush(col));
    Ellipse(bufdc,cx-w/2,cy-h/2,cx+w/2,cy+h/2);
    //
    DeleteDC(bufdc);
    InvalidateRect(hwnd,NULL,TRUE);
  }
  void OtherWndProc(UINT,WPARAM,LPARAM) {}

  void OnPaint(HDC hdc,const RECT &rc)
  { HDC bufdc=CreateCompatibleDC(hdc); SelectObject(bufdc,hbmBuffer);
    StretchBlt(hdc,0,0,rc.right,rc.bottom,bufdc,0,0,bw,bh,SRCCOPY);
    DeleteDC(bufdc);
  }
};








BOOL CALLBACK OptionsDlgProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ switch (msg)
  { case WM_INITDIALOG:
	  { CommonInit();
      //
      // TODO: set up the "options" dialog.
      // Also, in the dialog-editor, remember to remove the "Mute" checkbox if you don't have sound!
      //
      CheckDlgButton(hwnd,102,BigBlobs?BST_CHECKED:BST_UNCHECKED);
      //
    } return TRUE;

    case WM_COMMAND:
    { int id=LOWORD(wParam), code=HIWORD(wParam);
      if (id==102 && code==BN_CLICKED) BigBlobs =  (IsDlgButtonChecked(hwnd,102)==BST_CHECKED);
    } return TRUE;

    case WM_NOTIFY:
    { LPNMHDR nmh=(LPNMHDR)lParam; UINT code=nmh->code;
      switch (code)
      { case (PSN_APPLY):
        { //
          // TODO: save it to the registry
          //
          RegSave(_T("bigblobs"),BigBlobs);
          //
          SetWindowLong(hwnd,DWL_MSGRESULT,PSNRET_NOERROR);
        } return TRUE;
      }
    } return FALSE;
  }
  return FALSE;
}






// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------

BOOL VerifyPassword(HWND hwnd)
{ // Under NT, we return TRUE immediately. This lets the saver quit, and the system manages passwords.
  // Under '95, we call VerifyScreenSavePwd. This checks the appropriate registry key and, if necessary, pops up a verify dialog
  OSVERSIONINFO osv; osv.dwOSVersionInfoSize=sizeof(osv); GetVersionEx(&osv);
  if (osv.dwPlatformId==VER_PLATFORM_WIN32_NT) return TRUE;
  HINSTANCE hpwdcpl=::LoadLibrary(_T("PASSWORD.CPL"));
  if (hpwdcpl==NULL) {Debug(_T("Unable to load PASSWORD.CPL. Aborting"));return TRUE;}
  typedef BOOL (WINAPI *VERIFYSCREENSAVEPWD)(HWND hwnd);
  VERIFYSCREENSAVEPWD VerifyScreenSavePwd;
  VerifyScreenSavePwd=(VERIFYSCREENSAVEPWD)GetProcAddress(hpwdcpl,"VerifyScreenSavePwd");
  if (VerifyScreenSavePwd==NULL) {Debug(_T("Unable to get VerifyPwProc address. Aborting"));FreeLibrary(hpwdcpl);return TRUE;}
  Debug(_T("About to call VerifyPwProc")); BOOL bres=VerifyScreenSavePwd(hwnd); FreeLibrary(hpwdcpl);
  return bres;
}
void ChangePassword(HWND hwnd)
{ // This only ever gets called under '95, when started with the /a option.
  HINSTANCE hmpr=::LoadLibrary(_T("MPR.DLL"));
  if (hmpr==NULL) {Debug(_T("MPR.DLL not found: cannot change password."));return;}
  typedef VOID (WINAPI *PWDCHANGEPASSWORD) (LPCSTR lpcRegkeyname,HWND hwnd,UINT uiReserved1,UINT uiReserved2);
  PWDCHANGEPASSWORD PwdChangePassword=(PWDCHANGEPASSWORD)::GetProcAddress(hmpr,"PwdChangePasswordA");
  if (PwdChangePassword==NULL) {FreeLibrary(hmpr); Debug(_T("PwdChangeProc not found: cannot change password"));return;}
  PwdChangePassword("SCRSAVE",hwnd,0,0); FreeLibrary(hmpr);
}



void ReadGeneralRegistry()
{ PasswordDelay=15; PasswordDelayIndex=0; MouseThreshold=50; IsDialogActive=false; // default values in case they're not in registry
  LONG res; HKEY skey; DWORD valtype, valsize, val;
  res=RegOpenKeyEx(HKEY_CURRENT_USER,REGSTR_PATH_SETUP _T("\\Screen Savers"),0,KEY_READ,&skey);
  if (res!=ERROR_SUCCESS) return;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Password Delay"),0,&valtype,(LPBYTE)&val,&valsize); if (res==ERROR_SUCCESS) PasswordDelay=val;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Password Delay Index"),0,&valtype,(LPBYTE)&val,&valsize); if (res==ERROR_SUCCESS) PasswordDelayIndex=val;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Mouse Threshold"),0,&valtype,(LPBYTE)&val,&valsize);if (res==ERROR_SUCCESS) MouseThreshold=val;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Mouse Threshold Index"),0,&valtype,(LPBYTE)&val,&valsize);if (res==ERROR_SUCCESS) MouseThresholdIndex=val;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Mute Sound"),0,&valtype,(LPBYTE)&val,&valsize);     if (res==ERROR_SUCCESS) MuteSound=val;
  valsize=5*sizeof(TCHAR); res=RegQueryValueEx(skey,_T("Mouse Corners"),0,&valtype,(LPBYTE)Corners,&valsize);
  for (int i=0; i<4; i++) {if (Corners[i]!='Y' && Corners[i]!='N') Corners[i]='-';} Corners[4]=0;
  RegCloseKey(skey);
  //
  HotServices=FALSE;
  HINSTANCE sagedll=LoadLibrary(_T("sage.dll"));
	if (sagedll==0) sagedll=LoadLibrary(_T("scrhots.dll"));
  if (sagedll!=0)
  { typedef BOOL (WINAPI *SYSTEMAGENTDETECT)();
    SYSTEMAGENTDETECT detectproc=(SYSTEMAGENTDETECT)GetProcAddress(sagedll,"System_Agent_Detect");
    if (detectproc!=NULL) HotServices=detectproc();
    FreeLibrary(sagedll);
  }
}

void WriteGeneralRegistry()
{ LONG res; HKEY skey; DWORD val;
  res=RegCreateKeyEx(HKEY_CURRENT_USER,REGSTR_PATH_SETUP _T("\\Screen Savers"),0,0,0,KEY_ALL_ACCESS,0,&skey,0);
  if (res!=ERROR_SUCCESS) return;
  val=PasswordDelay; RegSetValueEx(skey,_T("Password Delay"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  val=PasswordDelayIndex; RegSetValueEx(skey,_T("Password Delay Index"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  val=MouseThreshold; RegSetValueEx(skey,_T("Mouse Threshold"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  val=MouseThresholdIndex; RegSetValueEx(skey,_T("Mouse Threshold Index"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  val=MuteSound?1:0; RegSetValueEx(skey,_T("Mute Sound"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  RegSetValueEx(skey,_T("Mouse Corners"),0,REG_SZ,(const BYTE*)Corners,5*sizeof(TCHAR));
  RegCloseKey(skey);
  //
  HINSTANCE sagedll=LoadLibrary(_T("sage.dll"));
	if (sagedll==0) sagedll=LoadLibrary(_T("scrhots.dll"));
  if (sagedll!=0)
  { typedef VOID (WINAPI *SCREENSAVERCHANGED)();
    SCREENSAVERCHANGED changedproc=(SCREENSAVERCHANGED)GetProcAddress(sagedll,"Screen_Saver_Changed");
    if (changedproc!=NULL) changedproc();
    FreeLibrary(sagedll);
  }
}


  
LRESULT CALLBACK SaverWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ TSaverWindow *sav; int id; HWND hmain;
  if (msg==WM_CREATE)
  { CREATESTRUCT *cs=(CREATESTRUCT*)lParam; id=*(int*)cs->lpCreateParams; SetWindowLong(hwnd,0,id);
    sav = new TSaverWindow(hwnd,id); SetWindowLong(hwnd,GWL_USERDATA,(LONG)sav);
    SaverWindow.push_back(sav);
  }
  else
  { sav=(TSaverWindow*)GetWindowLong(hwnd,GWL_USERDATA);
    id=GetWindowLong(hwnd,0);
  }
  if (id<=0) hmain=hwnd; else hmain=SaverWindow[0]->hwnd;
  //
  if (msg==WM_TIMER) sav->OnTimer();
  else if (msg==WM_PAINT) {PAINTSTRUCT ps; BeginPaint(hwnd,&ps); RECT rc; GetClientRect(hwnd,&rc); if (sav!=0) sav->OnPaint(ps.hdc,rc); EndPaint(hwnd,&ps);}
  else if (sav!=0) sav->OtherWndProc(msg,wParam,lParam);
  //
  switch (msg)
  { case WM_ACTIVATE: case WM_ACTIVATEAPP: case WM_NCACTIVATE:
    { TCHAR pn[100]; GetClassName((HWND)lParam,pn,100); 
      bool ispeer = (_tcsncmp(pn,_T("ScrClass"),8)==0);
      if (ScrMode==smSaver && !IsDialogActive && LOWORD(wParam)==WA_INACTIVE && !ispeer && !SCRDEBUG)
      { Debug(_T("WM_ACTIVATE: about to inactive window, so sending close")); ReallyClose=true; PostMessage(hmain,WM_CLOSE,0,0); return 0;
      }
    } break;
    case WM_SETCURSOR:
    { if (ScrMode==smSaver && !IsDialogActive && !SCRDEBUG) SetCursor(NULL);
      else SetCursor(LoadCursor(NULL,IDC_ARROW));
    } return 0;
    case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN:
    { if (ScrMode==smSaver && !IsDialogActive) {Debug(_T("WM_BUTTONDOWN: sending close")); ReallyClose=true; PostMessage(hmain,WM_CLOSE,0,0); return 0;}
    } break;
    case WM_MOUSEMOVE:
    { if (ScrMode==smSaver && !IsDialogActive && !SCRDEBUG)
      { POINT pt; GetCursorPos(&pt);
        int dx=pt.x-InitCursorPos.x; if (dx<0) dx=-dx; int dy=pt.y-InitCursorPos.y; if (dy<0) dy=-dy;
        if (dx>(int)MouseThreshold || dy>(int)MouseThreshold)
        { Debug(_T("WM_MOUSEMOVE: gone beyond threshold, sending close")); ReallyClose=true; PostMessage(hmain,WM_CLOSE,0,0);
        }
      }
    } return 0;
    case (WM_SYSCOMMAND):
    { if (ScrMode==smSaver)
      { if (wParam==SC_SCREENSAVE) {Debug(_T("WM_SYSCOMMAND: gobbling up a SC_SCREENSAVE to stop a new saver from running."));return 0;}
        if (wParam==SC_CLOSE && !SCRDEBUG) {Debug(_T("WM_SYSCOMMAND: gobbling up a SC_CLOSE"));return 0;}
      }
    } break;
    case (WM_CLOSE):
    { if (id>0) return 0; // secondary windows ignore this message
      if (id==-1) {DestroyWindow(hwnd); return 0;} // preview windows close obediently
      if (ReallyClose && !IsDialogActive)
      { Debug(_T("WM_CLOSE: maybe we need a password"));
        BOOL CanClose=TRUE;
        if (GetTickCount()-InitTime > 1000*PasswordDelay)
        { IsDialogActive=true; SendMessage(hwnd,WM_SETCURSOR,0,0);
          CanClose=VerifyPassword(hwnd);
          IsDialogActive=false; SendMessage(hwnd,WM_SETCURSOR,0,0); GetCursorPos(&InitCursorPos);
        }
        // note: all secondary monitors are owned by the primary. And we're the primary (id==0)
        // therefore, when we destroy ourself, windows will destroy the others first
        if (CanClose) {Debug(_T("WM_CLOSE: doing a DestroyWindow")); DestroyWindow(hwnd);}
        else {Debug(_T("WM_CLOSE: but failed password, so doing nothing"));}
      }
    } return 0;
    case (WM_DESTROY):
    { Debug(_T("WM_DESTROY."));
      SetWindowLong(hwnd,0,0); SetWindowLong(hwnd,GWL_USERDATA,0);
      for (vector<TSaverWindow*>::iterator i=SaverWindow.begin(); i!=SaverWindow.end(); i++) {if (sav==*i) *i=0;}
      delete sav;
      if ((id==0 && ScrMode==smSaver) || ScrMode==smPreview) PostQuitMessage(0);
    } break;
  }
  return DefWindowProc(hwnd,msg,wParam,lParam);
}






// ENUM-MONITOR-CALLBACK is part of DoSaver. Its stuff is in windef.h but
// requires WINVER>=0x0500. Well, we're doing LoadLibrary, so we know it's
// safe...
#ifndef SM_CMONITORS
DECLARE_HANDLE(HMONITOR);
#endif
//
BOOL CALLBACK EnumMonitorCallback(HMONITOR,HDC,LPRECT rc,LPARAM)
{ if (rc->left==0 && rc->top==0) monitors.insert(monitors.begin(),*rc);
  else monitors.push_back(*rc);
  return TRUE;
}

void DoSaver(HWND hparwnd, bool fakemulti)
{ if (ScrMode==smPreview)
  { RECT rc; GetWindowRect(hparwnd,&rc); monitors.push_back(rc);
  }
  else if (fakemulti)
  { int w=GetSystemMetrics(SM_CXSCREEN), x1=w/4, x2=w*2/3, h=x2-x1; RECT rc;
    rc.left=x1; rc.top=x1; rc.right=x1+h; rc.bottom=x1+h; monitors.push_back(rc);
    rc.left=0; rc.top=x1; rc.right=x1; rc.bottom=x1+x1; monitors.push_back(rc);
    rc.left=x2; rc.top=x1+h+x2-w; rc.right=w; rc.bottom=x1+h; monitors.push_back(rc);
  }
  else
  { int num_monitors=GetSystemMetrics(80); // 80=SM_CMONITORS
    if (num_monitors>1)
    { typedef BOOL (CALLBACK *LUMONITORENUMPROC)(HMONITOR,HDC,LPRECT,LPARAM);
      typedef BOOL (WINAPI *LUENUMDISPLAYMONITORS)(HDC,LPCRECT,LUMONITORENUMPROC,LPARAM);
      HINSTANCE husr=LoadLibrary(_T("user32.dll"));
      LUENUMDISPLAYMONITORS pEnumDisplayMonitors=0;
      if (husr!=NULL) pEnumDisplayMonitors=(LUENUMDISPLAYMONITORS)GetProcAddress(husr,"EnumDisplayMonitors");
      if (pEnumDisplayMonitors!=NULL) (*pEnumDisplayMonitors)(NULL,NULL,EnumMonitorCallback,NULL);
      if (husr!=NULL) FreeLibrary(husr);
    }
    if (monitors.size()==0)
    { RECT rc; rc.left=0; rc.top=0; rc.right=GetSystemMetrics(SM_CXSCREEN); rc.bottom=GetSystemMetrics(SM_CYSCREEN);
      monitors.push_back(rc);
    }
  }
  //
  HWND hwnd=0;
  if (ScrMode==smPreview)
  { RECT rc; GetWindowRect(hparwnd,&rc); int w=rc.right-rc.left, h=rc.bottom-rc.top;  
    int id=-1; hwnd=CreateWindowEx(0,_T("ScrClass"),_T(""),WS_CHILD|WS_VISIBLE,0,0,w,h,hparwnd,NULL,hInstance,&id);
  }
  else
  { GetCursorPos(&InitCursorPos); InitTime=GetTickCount();
    for (int i=0; i<(int)monitors.size(); i++)
    { const RECT &rc = monitors[i];
      DWORD exstyle=WS_EX_TOPMOST; if (SCRDEBUG) exstyle=0;
      HWND hthis = CreateWindowEx(exstyle,_T("ScrClass"),_T(""),WS_POPUP|WS_VISIBLE,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,hwnd,NULL,hInstance,&i);
      if (i==0) hwnd=hthis;
    }
  }
  if (hwnd!=NULL)
  { UINT oldval;
    if (ScrMode==smSaver) SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,1,&oldval,0);
    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    { TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    if (ScrMode==smSaver) SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,0,&oldval,0);
  }
  //
  SaverWindow.clear();
  return;
}




BOOL CALLBACK GeneralDlgProc(HWND hwnd,UINT msg,WPARAM,LPARAM lParam)
{ switch (msg)
  { case (WM_INITDIALOG):
	  { ShowWindow(GetDlgItem(hwnd,HotServices?102:101),SW_HIDE);
      SetDlgItemText(hwnd,112,Corners);
      SendDlgItemMessage(hwnd,109,CB_ADDSTRING,0,(LPARAM)_T("seconds"));
      SendDlgItemMessage(hwnd,109,CB_ADDSTRING,0,(LPARAM)_T("minutes"));
      SendDlgItemMessage(hwnd,109,CB_SETCURSEL,PasswordDelayIndex,0);
      int n=PasswordDelay; if (PasswordDelayIndex==1) n/=60;
      TCHAR c[16]; wsprintf(c,_T("%i"),n); SetDlgItemText(hwnd,107,c);
      SendDlgItemMessage(hwnd,108,UDM_SETRANGE,0,MAKELONG(99,0));
      SendDlgItemMessage(hwnd,105,CB_ADDSTRING,0,(LPARAM)_T("High"));
      SendDlgItemMessage(hwnd,105,CB_ADDSTRING,0,(LPARAM)_T("Normal"));
      SendDlgItemMessage(hwnd,105,CB_ADDSTRING,0,(LPARAM)_T("Low"));
      SendDlgItemMessage(hwnd,105,CB_ADDSTRING,0,(LPARAM)_T("Keyboard only (ignore mouse movement)"));
      SendDlgItemMessage(hwnd,105,CB_SETCURSEL,MouseThresholdIndex,0);
      if (MuteSound) CheckDlgButton(hwnd,113,BST_CHECKED);
      OSVERSIONINFO ver; ZeroMemory(&ver,sizeof(ver)); ver.dwOSVersionInfoSize=sizeof(ver); GetVersionEx(&ver);
      for (int i=106; i<111 && ver.dwPlatformId!=VER_PLATFORM_WIN32_WINDOWS; i++) ShowWindow(GetDlgItem(hwnd,i),SW_HIDE); 
    } return TRUE;
    case (WM_NOTIFY):
    { LPNMHDR nmh=(LPNMHDR)lParam; UINT code=nmh->code;
      switch (code)
      { case (PSN_APPLY):
        { GetDlgItemText(hwnd,112,Corners,5);                   
          PasswordDelayIndex=SendDlgItemMessage(hwnd,109,CB_GETCURSEL,0,0);
          TCHAR c[16]; GetDlgItemText(hwnd,107,c,16); int n=_ttoi(c); if (PasswordDelayIndex==1) n*=60; PasswordDelay=n;
          MouseThresholdIndex=SendDlgItemMessage(hwnd,105,CB_GETCURSEL,0,0);
          if (MouseThresholdIndex==0) MouseThreshold=0;
          else if (MouseThresholdIndex==1) MouseThreshold=200;
          else if (MouseThresholdIndex==2) MouseThreshold=400;
          else MouseThreshold=999999;
          MuteSound = (IsDlgButtonChecked(hwnd,113)==BST_CHECKED);
          WriteGeneralRegistry();
          SetWindowLong(hwnd,DWL_MSGRESULT,PSNRET_NOERROR);
        } return TRUE;
      }
    } return FALSE;
  }
  return FALSE;
}



//
// MONITOR CONTROL -- either corners or a preview
//

LRESULT CALLBACK MonitorWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ switch (msg)
  { case WM_CREATE:
    { TCHAR c[5]; GetWindowText(hwnd,c,5); if (*c!=0) return 0;
      int id=-1; RECT rc; SendMessage(hwnd,SCRM_GETMONITORAREA,0,(LPARAM)&rc);
      CreateWindow(_T("ScrClass"),_T(""),WS_CHILD|WS_VISIBLE,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,hwnd,NULL,hInstance,&id);
    } return 0;
    case WM_PAINT:
    { if (hbmmonitor==0) hbmmonitor=LoadBitmap(hInstance,_T("Monitor"));
      RECT rc; GetClientRect(hwnd,&rc);
      //
      PAINTSTRUCT ps; BeginPaint(hwnd,&ps);
      HBITMAP hback = (HBITMAP)GetWindowLong(hwnd,GWL_USERDATA);
      if (hback!=0)
      { BITMAP bmp; GetObject(hback,sizeof(bmp),&bmp);
        if (bmp.bmWidth!=rc.right || bmp.bmHeight!=rc.bottom) {DeleteObject(hback); hback=0;}
      }
      if (hback==0) {hback=CreateCompatibleBitmap(ps.hdc,rc.right,rc.bottom); SetWindowLong(hwnd,GWL_USERDATA,(LONG)hback);}
      HDC backdc=CreateCompatibleDC(ps.hdc);
      HGDIOBJ holdback=SelectObject(backdc,hback);
      BitBlt(backdc,0,0,rc.right,rc.bottom,ps.hdc,0,0,SRCCOPY);
      //
      TCHAR corners[5]; GetWindowText(hwnd,corners,5);
      HDC hdc=CreateCompatibleDC(ps.hdc);
      HGDIOBJ hold=SelectObject(hdc,hbmmonitor);
      StretchBlt(backdc,0,0,rc.right,rc.bottom,hdc,0,0,184,170,SRCAND);
      StretchBlt(backdc,0,0,rc.right,rc.bottom,hdc,184,0,184,170,SRCINVERT);
      RECT crc; SendMessage(hwnd,SCRM_GETMONITORAREA,0,(LPARAM)&crc);
      //
      if (*corners!=0) FillRect(backdc,&crc,GetSysColorBrush(COLOR_DESKTOP));
      for (int i=0; i<4 && *corners!=0; i++)
      { RECT crc; SendMessage(hwnd,SCRM_GETMONITORAREA,i+1,(LPARAM)&crc);
        int y=0; if (corners[i]=='Y') y=22; else if (corners[i]=='N') y=44;
        BitBlt(backdc,crc.left,crc.top,crc.right-crc.left,crc.bottom-crc.top,hdc,368,y,SRCCOPY);
        if (!HotServices) 
        { DWORD col=GetSysColor(COLOR_DESKTOP);
          for (int y=crc.top; y<crc.bottom; y++)
          { for (int x=crc.left+(y&1); x<crc.right; x+=2) SetPixel(backdc,x,y,col);
          }
        }
      }
      SelectObject(hdc,hold);
      DeleteDC(hdc);
      //
      BitBlt(ps.hdc,0,0,rc.right,rc.bottom,backdc,0,0,SRCCOPY);
      SelectObject(backdc,holdback);
      DeleteDC(backdc);
      EndPaint(hwnd,&ps);
    } return 0;
    case SCRM_GETMONITORAREA:
    { RECT *prc=(RECT*)lParam;
      if (hbmmonitor==0) hbmmonitor=LoadBitmap(hInstance,_T("Monitor"));
      // those are the client coordinates unscalled
      RECT wrc; GetClientRect(hwnd,&wrc); int ww=wrc.right, wh=wrc.bottom;
      RECT rc; rc.left=16*ww/184; rc.right=168*ww/184; rc.top=17*wh/170; rc.bottom=130*wh/170;
      *prc=rc; if (wParam==0) return 0;
      if (wParam==1) {prc->right=rc.left+24; prc->bottom=rc.top+22;}
      else if (wParam==2) {prc->left=rc.right-24; prc->bottom=rc.top+22;}
      else if (wParam==3) {prc->left=rc.right-24; prc->top=rc.bottom-22;}
      else if (wParam==4) {prc->right=rc.left+24; prc->top=rc.bottom-22;}
    } return 0;
    case WM_LBUTTONDOWN:
    { if (!HotServices) return 0;
      int x=LOWORD(lParam), y=HIWORD(lParam);
      TCHAR corners[5]; GetWindowText(hwnd,corners,5);
      if (corners[0]==0) return 0;
      int click=-1; for (int i=0; i<4; i++)
      { RECT rc; SendMessage(hwnd,SCRM_GETMONITORAREA,i+1,(LPARAM)&rc);
        if (x>=rc.left && y>=rc.top && x<rc.right && y<rc.bottom) {click=i; break;}
      }
      if (click==-1) return 0;
      for (int j=0; j<4; j++)
      { if (corners[j]!='-' && corners[j]!='Y' && corners[j]!='N') corners[j]='-';
      }
      corners[4]=0;
      //
      HMENU hmenu=CreatePopupMenu();
      MENUITEMINFO mi; ZeroMemory(&mi,sizeof(mi)); mi.cbSize=sizeof(MENUITEMINFO);
      mi.fMask=MIIM_TYPE|MIIM_ID|MIIM_STATE|MIIM_DATA;
      mi.fType=MFT_STRING|MFT_RADIOCHECK;
      mi.wID='N'; mi.fState=MFS_ENABLED; if (corners[click]=='N') mi.fState|=MFS_CHECKED;
      mi.dwTypeData=_T("Never"); mi.cch=sizeof(TCHAR)*_tcslen(mi.dwTypeData);
      InsertMenuItem(hmenu,0,TRUE,&mi);
      mi.wID='Y'; mi.fState=MFS_ENABLED; if (corners[click]=='Y') mi.fState|=MFS_CHECKED;
      mi.dwTypeData=_T("Now"); mi.cch=sizeof(TCHAR)*_tcslen(mi.dwTypeData);
      InsertMenuItem(hmenu,0,TRUE,&mi);
      mi.wID='-'; mi.fState=MFS_ENABLED; if (corners[click]!='Y' && corners[click]!='N') mi.fState|=MFS_CHECKED;
      mi.dwTypeData=_T("Default"); mi.cch=sizeof(TCHAR)*_tcslen(mi.dwTypeData);
      InsertMenuItem(hmenu,0,TRUE,&mi);
      POINT pt; pt.x=x; pt.y=y; ClientToScreen(hwnd,&pt);
      int cmd = TrackPopupMenuEx(hmenu,TPM_RETURNCMD|TPM_RIGHTBUTTON,pt.x,pt.y,hwnd,NULL);
      if (cmd!=0) corners[click]=(char)cmd;
      corners[4]=0; SetWindowText(hwnd,corners);
      InvalidateRect(hwnd,NULL,FALSE);
    } return 0;
    case WM_DESTROY:
    { HBITMAP hback = (HBITMAP)SetWindowLong(hwnd,GWL_USERDATA,0);
      if (hback!=0) DeleteObject(hback);
    } return 0;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}






BOOL CALLBACK AboutDlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM)
{ if (msg==WM_INITDIALOG)
  { SetDlgItemText(hdlg,101,SaverName.c_str());
    SetDlgItemUrl(hdlg,102,_T("http://www.wischik.com/lu/scr/"));
    SetDlgItemText(hdlg,102,_T("www.wischik.com/scr"));
    return TRUE;
  }
  else if (msg==WM_COMMAND)
  { int id=LOWORD(wParam);
    if (id==IDOK || id==IDCANCEL) EndDialog(hdlg,id);
    return TRUE;
  } 
  else return FALSE;
}


//
// PROPERTY SHEET SUBCLASSING -- this is to stick an "About" option on the sysmenu.
//
WNDPROC OldSubclassProc=0;
LRESULT CALLBACK PropertysheetSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ if (msg==WM_SYSCOMMAND && wParam==3500)
  { DialogBox(hInstance,_T("DLG_ABOUT"),hwnd,AboutDlgProc);
    return 0;
  } 
  if (OldSubclassProc!=NULL) return CallWindowProc(OldSubclassProc,hwnd,msg,wParam,lParam);
  else return DefWindowProc(hwnd,msg,wParam,lParam);
}

int CALLBACK PropertysheetCallback(HWND hwnd,UINT msg,LPARAM)
{ if (msg!=PSCB_INITIALIZED) return 0;
  HMENU hsysmenu=GetSystemMenu(hwnd,FALSE);
  AppendMenu(hsysmenu,MF_SEPARATOR,1,_T("-"));
  AppendMenu(hsysmenu,MF_STRING,3500,_T("About..."));
  OldSubclassProc=(WNDPROC)SetWindowLong(hwnd,GWL_WNDPROC,(LONG)PropertysheetSubclassProc);
  return 0;
}


void DoConfig(HWND hpar)
{ hiconbg = (HICON)LoadImage(hInstance,MAKEINTRESOURCE(1),IMAGE_ICON,GetSystemMetrics(SM_CXICON),GetSystemMetrics(SM_CYICON),0);
  hiconsm = (HICON)LoadImage(hInstance,MAKEINTRESOURCE(1),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0);
  //  
  PROPSHEETHEADER psh; ZeroMemory(&psh,sizeof(psh));
  PROPSHEETPAGE psp[2]; ZeroMemory(psp,2*sizeof(PROPSHEETPAGE));
  psp[0].dwSize=sizeof(psp[0]);
  psp[0].dwFlags=PSP_DEFAULT;
  psp[0].hInstance=hInstance; 
  psp[0].pszTemplate=_T("DLG_GENERAL");
  psp[0].pfnDlgProc=GeneralDlgProc;
  psp[1].dwSize=sizeof(psp[1]);
  psp[1].dwFlags=PSP_DEFAULT;
  psp[1].hInstance=hInstance;  
  psp[1].pszTemplate=_T("DLG_OPTIONS");
  psp[1].pfnDlgProc=OptionsDlgProc;
  psh.dwSize=sizeof(psh);
  psh.dwFlags=PSH_NOAPPLYNOW | PSH_PROPSHEETPAGE | PSH_USEHICON | PSH_USECALLBACK;
  psh.hwndParent=hpar;
  psh.hInstance=hInstance;
  psh.hIcon=hiconsm;
  tstring cap=_T("Options for ")+SaverName; psh.pszCaption=cap.c_str();
  psh.nPages=2;
  psh.nStartPage=1;
  psh.ppsp=psp;
  psh.pfnCallback=PropertysheetCallback;
  Debug(_T("Config..."));
  PropertySheet(&psh);
  Debug(_T("Config done."));
  if (hiconbg!=0) DestroyIcon(hiconbg); hiconbg=0;
  if (hiconsm!=0) DestroyIcon(hiconsm); hiconsm=0;
  if (hbmmonitor!=0) DeleteObject(hbmmonitor); hbmmonitor=0;
}


// This routine is for using ScrPrev. It's so that you can start the saver
// with the command line /p scrprev and it runs itself in a preview window.
// You must first copy ScrPrev somewhere in your search path
HWND CheckForScrprev()
{ HWND hwnd=FindWindow(_T("Scrprev"),NULL); // looks for the Scrprev class
  if (hwnd==NULL) // try to load it
  { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si,sizeof(si)); ZeroMemory(&pi,sizeof(pi));
    si.cb=sizeof(si);
    TCHAR cmd[MAX_PATH]; _tcscpy(cmd,_T("Scrprev")); // unicode CreateProcess requires it writeable
    BOOL cres=CreateProcess(NULL,cmd,0,0,FALSE,CREATE_NEW_PROCESS_GROUP | CREATE_DEFAULT_ERROR_MODE,
                            0,0,&si,&pi);
    if (!cres) {Debug(_T("Error creating scrprev process")); return NULL;}
    DWORD wres=WaitForInputIdle(pi.hProcess,2000);
    if (wres==WAIT_TIMEOUT) {Debug(_T("Scrprev never becomes idle")); return NULL;}
    if (wres==0xFFFFFFFF) {Debug(_T("ScrPrev, misc error after ScrPrev execution"));return NULL;}
    hwnd=FindWindow(_T("Scrprev"),NULL);
  }
  if (hwnd==NULL) {Debug(_T("Unable to find Scrprev window")); return NULL;}
  ::SetForegroundWindow(hwnd);
  hwnd=GetWindow(hwnd,GW_CHILD);
  if (hwnd==NULL) {Debug(_T("Couldn't find Scrprev child")); return NULL;}
  return hwnd;
}


void DoInstall()
{ TCHAR windir[MAX_PATH]; GetWindowsDirectory(windir,MAX_PATH);
  TCHAR tfn[MAX_PATH]; UINT ures=GetTempFileName(windir,_T("pst"),0,tfn);
  if (ures==0) {MessageBox(NULL,_T("You must be logged on as system administrator to install screen savers"),_T("Saver Install"),MB_ICONINFORMATION|MB_OK); return;}
  DeleteFile(tfn);
  tstring fn=tstring(windir)+_T("\\")+SaverName+_T(".scr");
  DWORD attr = GetFileAttributes(fn.c_str());
  bool exists = (attr!=0xFFFFFFFF);
  tstring msg=_T("Do you want to install '")+SaverName+_T("' ?");
  if (exists) msg+=_T("\r\n\r\n(This will replace the version that you have currently)");
  int res=MessageBox(NULL,msg.c_str(),_T("Saver Install"),MB_YESNOCANCEL);
  if (res!=IDYES) return;
  TCHAR cfn[MAX_PATH]; GetModuleFileName(hInstance,cfn,MAX_PATH);
  SetCursor(LoadCursor(NULL,IDC_WAIT));
  BOOL bres = CopyFile(cfn,fn.c_str(),FALSE);
  if (!bres)
  { tstring msg = _T("There was an error installing the saver.\r\n\r\n\"")+GetLastErrorString()+_T("\"");
    MessageBox(NULL,msg.c_str(),_T("Saver Install"),MB_ICONERROR|MB_OK);
    SetCursor(LoadCursor(NULL,IDC_ARROW));
    return;
  }
  LONG lres; HKEY skey; DWORD disp; tstring val;
  tstring key=REGSTR_PATH_UNINSTALL _T("\\")+SaverName;
  lres=RegCreateKeyEx(HKEY_LOCAL_MACHINE,key.c_str(),0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&skey,&disp);
  if (lres==ERROR_SUCCESS)
  { val=SaverName+_T(" saver"); RegSetValueEx(skey,_T("DisplayName"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    val=_T("\"")+fn+_T("\" /u"); RegSetValueEx(skey,_T("UninstallString"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    RegSetValueEx(skey,_T("UninstallPath"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    val=_T("\"")+fn+_T("\""); RegSetValueEx(skey,_T("ModifyPath"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    val=fn; RegSetValueEx(skey,_T("DisplayIcon"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    TCHAR url[1024]; int ures=LoadString(hInstance,2,url,1024); if (ures!=0) RegSetValueEx(skey,_T("HelpLink"),0,REG_SZ,(const BYTE*)url,sizeof(TCHAR)*(_tcslen(url)+1));
    RegCloseKey(skey);
  }
  SHELLEXECUTEINFO sex; ZeroMemory(&sex,sizeof(sex)); sex.cbSize=sizeof(sex);
  sex.fMask=SEE_MASK_NOCLOSEPROCESS;
  sex.lpVerb=_T("install");
  sex.lpFile=fn.c_str();
  sex.nShow=SW_SHOWNORMAL;
  bres = ShellExecuteEx(&sex);
  if (!bres) {SetCursor(LoadCursor(NULL,IDC_ARROW)); MessageBox(NULL,_T("The saver has been installed"),SaverName.c_str(),MB_OK); return;}
  WaitForInputIdle(sex.hProcess,2000);
  CloseHandle(sex.hProcess);
  SetCursor(LoadCursor(NULL,IDC_ARROW));
}


void DoUninstall()
{ tstring key=REGSTR_PATH_UNINSTALL _T("\\")+SaverName;
  RegDeleteKey(HKEY_LOCAL_MACHINE,key.c_str());
  TCHAR fn[MAX_PATH]; GetModuleFileName(hInstance,fn,MAX_PATH);
  SetFileAttributes(fn,FILE_ATTRIBUTE_NORMAL); // remove readonly if necessary
  BOOL res = MoveFileEx(fn,NULL,MOVEFILE_DELAY_UNTIL_REBOOT);
  //
  const TCHAR *c=fn, *lastslash=c;
  while (*c!=0) {if (*c=='\\' || *c=='/') lastslash=c+1; c++;}
  tstring cap=SaverName+_T(" uninstaller");
  tstring msg;
  if (res) msg=_T("Uninstall completed. The saver will be removed next time you reboot.");
  else msg=_T("There was a problem uninstalling.\r\n")
           _T("To complete the uninstall manually, you should go into your Windows ")
           _T("directory and delete the file '")+tstring(lastslash)+_T("'");
  MessageBox(NULL,msg.c_str(),cap.c_str(),MB_OK);
}




// --------------------------------------------------------------------------------
// SetDlgItemUrl(hwnd,IDC_MYSTATIC,"http://www.wischik.com/lu");
//   This routine turns a dialog's static text control into an underlined hyperlink.
//   You can call it in your WM_INITDIALOG, or anywhere.
//   It will also set the text of the control... if you want to change the text
//   back, you can just call SetDlgItemText() afterwards.
// --------------------------------------------------------------------------------
void SetDlgItemUrl(HWND hdlg,int id,const TCHAR *url); 

// Implementation notes:
// We have to subclass both the static control (to set its cursor, to respond to click)
// and the dialog procedure (to set the font of the static control). Here are the two
// subclasses:
LRESULT CALLBACK UrlCtlProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK UrlDlgProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
// When the user calls SetDlgItemUrl, then the static-control-subclass is added
// if it wasn't already there, and the dialog-subclass is added if it wasn't
// already there. Both subclasses are removed in response to their respective
// WM_DESTROY messages. Also, each subclass stores a property in its window,
// which is a HGLOBAL handle to a GlobalAlloc'd structure:
typedef struct {TCHAR *url; WNDPROC oldproc; HFONT hf; HBRUSH hb;} TUrlData;
// I'm a miser and only defined a single structure, which is used by both
// the control-subclass and the dialog-subclass. Both of them use 'oldproc' of course.
// The control-subclass only uses 'url' (in response to WM_LBUTTONDOWN),
// and the dialog-subclass only uses 'hf' and 'hb' (in response to WM_CTLCOLORSTATIC)
// There is one sneaky thing to note. We create our underlined font *lazily*.
// Initially, hf is just NULL. But the first time the subclassed dialog received
// WM_CTLCOLORSTATIC, we sneak a peak at the font that was going to be used for
// the control, and we create our own copy of it but including the underline style.
// This way our code works fine on dialogs of any font.

// SetDlgItemUrl: this is the routine that sets up the subclassing.
void SetDlgItemUrl(HWND hdlg,int id,const TCHAR *url) 
{ // nb. vc7 has crummy warnings about 32/64bit. My code's perfect! That's why I hide the warnings.
  #pragma warning( push )
  #pragma warning( disable: 4312 4244 )
  // First we'll subclass the edit control
  HWND hctl = GetDlgItem(hdlg,id);
  SetWindowText(hctl,url);
  HGLOBAL hold = (HGLOBAL)GetProp(hctl,_T("href_dat"));
  if (hold!=NULL) // if it had been subclassed before, we merely need to tell it the new url
  { TUrlData *ud = (TUrlData*)GlobalLock(hold);
    delete[] ud->url;
    ud->url=new TCHAR[_tcslen(url)+1]; _tcscpy(ud->url,url);
  }
  else
  { HGLOBAL hglob = GlobalAlloc(GMEM_MOVEABLE,sizeof(TUrlData));
    TUrlData *ud = (TUrlData*)GlobalLock(hglob);
    ud->oldproc = (WNDPROC)GetWindowLong(hctl,GWL_WNDPROC);
    ud->url=new TCHAR[_tcslen(url)+1]; _tcscpy(ud->url,url);
    ud->hf=0; ud->hb=0;
    GlobalUnlock(hglob);
    SetProp(hctl,_T("href_dat"),hglob);
    SetWindowLong(hctl,GWL_WNDPROC,(LONG)UrlCtlProc);
  }
  //
  // Second we subclass the dialog
  hold = (HGLOBAL)GetProp(hdlg,_T("href_dlg"));
  if (hold==NULL)
  { HGLOBAL hglob = GlobalAlloc(GMEM_MOVEABLE,sizeof(TUrlData));
    TUrlData *ud = (TUrlData*)GlobalLock(hglob);
    ud->url=0;
    ud->oldproc = (WNDPROC)GetWindowLong(hdlg,GWL_WNDPROC);
    ud->hb=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
    ud->hf=0; // the font will be created lazilly, the first time WM_CTLCOLORSTATIC gets called
    GlobalUnlock(hglob);
    SetProp(hdlg,_T("href_dlg"),hglob);
    SetWindowLong(hdlg,GWL_WNDPROC,(LONG)UrlDlgProc);
  }
  #pragma warning( pop )
}

// UrlCtlProc: this is the subclass procedure for the static control
LRESULT CALLBACK UrlCtlProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ HGLOBAL hglob = (HGLOBAL)GetProp(hwnd,_T("href_dat"));
  if (hglob==NULL) return DefWindowProc(hwnd,msg,wParam,lParam);
  TUrlData *oud=(TUrlData*)GlobalLock(hglob); TUrlData ud=*oud;
  GlobalUnlock(hglob); // I made a copy of the structure just so I could GlobalUnlock it now, to be more local in my code
  switch (msg)
  { case WM_DESTROY:
    { RemoveProp(hwnd,_T("href_dat")); GlobalFree(hglob);
      if (ud.url!=0) delete[] ud.url;
      // nb. remember that ud.url is just a pointer to a memory block. It might look weird
      // for us to delete ud.url instead of oud->url, but they're both equivalent.
    } break;
    case WM_LBUTTONDOWN:
    { HWND hdlg=GetParent(hwnd); if (hdlg==0) hdlg=hwnd;
      ShellExecute(hdlg,_T("open"),ud.url,NULL,NULL,SW_SHOWNORMAL);
    } break;
    case WM_SETCURSOR:
    { HCURSOR hc=LoadCursor(NULL,MAKEINTRESOURCE(32649)); // =IDC_HAND
      if (hc==0) hc=LoadCursor(NULL,IDC_ARROW); // systems before Win2k didn't have the hand
      SetCursor(hc);
      return TRUE;
    } 
    case WM_NCHITTEST:
    { return HTCLIENT; // because normally a static returns HTTRANSPARENT, so disabling WM_SETCURSOR
    } 
  }
  return CallWindowProc(ud.oldproc,hwnd,msg,wParam,lParam);
}
  
// UrlDlgProc: this is the subclass procedure for the dialog
LRESULT CALLBACK UrlDlgProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ HGLOBAL hglob = (HGLOBAL)GetProp(hwnd,_T("href_dlg"));
  if (hglob==NULL) return DefWindowProc(hwnd,msg,wParam,lParam);
  TUrlData *oud=(TUrlData*)GlobalLock(hglob); TUrlData ud=*oud;
  GlobalUnlock(hglob);
  switch (msg)
  { case WM_DESTROY:
    { RemoveProp(hwnd,_T("href_dlg")); GlobalFree(hglob);
      if (ud.hb!=0) DeleteObject(ud.hb);
      if (ud.hf!=0) DeleteObject(ud.hf);
    } break;
    case WM_CTLCOLORSTATIC:
    { HDC hdc=(HDC)wParam; HWND hctl=(HWND)lParam;
      // To check whether to handle this control, we look for its subclassed property!
      HANDLE hprop=GetProp(hctl,_T("href_dat")); if (hprop==NULL) return CallWindowProc(ud.oldproc,hwnd,msg,wParam,lParam);
      // There has been a lot of faulty discussion in the newsgroups about how to change
      // the text colour of a static control. Lots of people mess around with the
      // TRANSPARENT text mode. That is incorrect. The correct solution is here:
      // (1) Leave the text opaque. This will allow us to re-SetDlgItemText without it looking wrong.
      // (2) SetBkColor. This background colour will be used underneath each character cell.
      // (3) return HBRUSH. This background colour will be used where there's no text.
      SetTextColor(hdc,RGB(0,0,255));
      SetBkColor(hdc,GetSysColor(COLOR_BTNFACE));
      if (ud.hf==0)
      { // we use lazy creation of the font. That's so we can see font was currently being used.
        TEXTMETRIC tm; GetTextMetrics(hdc,&tm);
        LOGFONT lf;
        lf.lfHeight=tm.tmHeight;
        lf.lfWidth=0;
        lf.lfEscapement=0;
        lf.lfOrientation=0;
        lf.lfWeight=tm.tmWeight;
        lf.lfItalic=tm.tmItalic;
        lf.lfUnderline=TRUE;
        lf.lfStrikeOut=tm.tmStruckOut;
        lf.lfCharSet=tm.tmCharSet;
        lf.lfOutPrecision=OUT_DEFAULT_PRECIS;
        lf.lfClipPrecision=CLIP_DEFAULT_PRECIS;
        lf.lfQuality=DEFAULT_QUALITY;
        lf.lfPitchAndFamily=tm.tmPitchAndFamily;
        GetTextFace(hdc,LF_FACESIZE,lf.lfFaceName);
        ud.hf=CreateFontIndirect(&lf);
        TUrlData *oud = (TUrlData*)GlobalLock(hglob); oud->hf=ud.hf; GlobalUnlock(hglob);
      }
      SelectObject(hdc,ud.hf);
      // Note: the win32 docs say to return an HBRUSH, typecast as a BOOL. But they
      // fail to explain how this will work in 64bit windows where an HBRUSH is 64bit.
      // I have supressed the warnings for now, because I hate them...
      #pragma warning( push )
      #pragma warning( disable: 4311 )
      return (BOOL)ud.hb;
      #pragma warning( pop )
    }  
  }
  return CallWindowProc(ud.oldproc,hwnd,msg,wParam,lParam);
}



inline void Debug(const tstring s)
{ if (DebugFile==_T("")) return;
  if (DebugFile==_T("OutputDebugString")) {tstring err=s+_T("\r\n"); OutputDebugString(err.c_str());}
  else {FILE *f = _tfopen(DebugFile.c_str(),_T("a+t")); _ftprintf(f,_T("%s\n"),s.c_str()); fclose(f);}
}

tstring GetLastErrorString()
{ LPVOID lpMsgBuf;
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,NULL,
    GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
  tstring s((TCHAR*)lpMsgBuf);
  LocalFree( lpMsgBuf );
  return s;
}


void RegSave(const tstring name,DWORD type,void*buf,int size)
{ tstring path = _T("Software\\Scrplus\\")+SaverName;
  HKEY skey; LONG res=RegCreateKeyEx(HKEY_CURRENT_USER,path.c_str(),0,0,0,KEY_ALL_ACCESS,0,&skey,0);
  if (res!=ERROR_SUCCESS) return;
  RegSetValueEx(skey,name.c_str(),0,type,(const BYTE*)buf,size);
  RegCloseKey(skey);
}
bool RegLoadDword(const tstring name,DWORD *buf)
{ tstring path = _T("Software\\Scrplus\\")+SaverName;
  HKEY skey; LONG res=RegOpenKeyEx(HKEY_CURRENT_USER,path.c_str(),0,KEY_READ,&skey);
  if (res!=ERROR_SUCCESS) return false;
  DWORD size=sizeof(DWORD);
  res=RegQueryValueEx(skey,name.c_str(),0,0,(LPBYTE)buf,&size);
  RegCloseKey(skey);
  return (res==ERROR_SUCCESS);
}

void RegSave(const tstring name,int val)
{ DWORD v=val; RegSave(name,REG_DWORD,&v,sizeof(v));
}
void RegSave(const tstring name,bool val)
{ RegSave(name,val?1:0);
}
void RegSave(const tstring name,tstring val)
{ RegSave(name,REG_SZ,(void*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
}
int RegLoad(const tstring name,int def)
{ DWORD val; bool res=RegLoadDword(name,&val);
  return res?val:def;
}
bool RegLoad(const tstring name,bool def)
{ int b=RegLoad(name,def?1:0); return (b!=0);
}
tstring RegLoad(const tstring name,tstring def)
{ tstring path = _T("Software\\Scrplus\\")+SaverName;
  HKEY skey; LONG res=RegOpenKeyEx(HKEY_CURRENT_USER,path.c_str(),0,KEY_READ,&skey);
  if (res!=ERROR_SUCCESS) return def;
  DWORD size=0; res=RegQueryValueEx(skey,name.c_str(),0,0,0,&size);
  if (res!=ERROR_SUCCESS) {RegCloseKey(skey); return def;}
  TCHAR *buf = new TCHAR[size];
  RegQueryValueEx(skey,name.c_str(),0,0,(LPBYTE)buf,&size);
  tstring s(buf); delete[] buf;
  RegCloseKey(skey);
  return s;
}



int WINAPI WinMain(HINSTANCE h,HINSTANCE,LPSTR,int)
{ hInstance=h;
  TCHAR name[MAX_PATH]; int sres=LoadString(hInstance,1,name,MAX_PATH);
  if (sres==0) {MessageBox(NULL,_T("Must store saver name as String Resource 1"),_T("Saver"),MB_ICONERROR|MB_OK);return 0;}
  SaverName=name;
  //
  TCHAR mod[MAX_PATH]; GetModuleFileName(hInstance,mod,MAX_PATH); tstring smod(mod);
  bool isexe = (smod.find(_T(".exe"))!=tstring::npos || smod.find(_T(".EXE"))!=tstring::npos);
  //
  TCHAR *c=GetCommandLine();
  if (*c=='\"') {c++; while (*c!=0 && *c!='\"') c++; if (*c=='\"') c++;} else {while (*c!=0 && *c!=' ') c++;}
  while (*c==' ') c++;
  HWND hwnd=NULL; bool fakemulti=false;
  if (*c==0) {if (isexe) ScrMode=smInstall; else ScrMode=smConfig; hwnd=NULL;}
  else
  { if (*c=='-' || *c=='/') c++;
    if (*c=='u' || *c=='U') ScrMode=smUninstall;
    if (*c=='p' || *c=='P' || *c=='l' || *c=='L')
    { c++; while (*c==' ' || *c==':') c++;
      if (_tcsicmp(c,_T("scrprev"))==0) hwnd=CheckForScrprev(); else hwnd=(HWND)_ttoi(c); 
      ScrMode=smPreview;
    }
    else if (*c=='s' || *c=='S') {ScrMode=smSaver; fakemulti=(c[1]=='m'||c[1]=='M');}
    else if (*c=='c' || *c=='C') {c++; while (*c==' ' || *c==':') c++; if (*c==0) hwnd=GetForegroundWindow(); else hwnd=(HWND)_ttoi(c); ScrMode=smConfig;}
    else if (*c=='a' || *c=='A') {c++; while (*c==' ' || *c==':') c++; hwnd=(HWND)_ttoi(c); ScrMode=smPassword;}
  }
  //
  if (ScrMode==smInstall) {DoInstall(); return 0;}
  if (ScrMode==smUninstall) {DoUninstall(); return 0;}
  if (ScrMode==smPassword) {ChangePassword(hwnd); return 0;}
  //
  ReadGeneralRegistry();
  //
  INITCOMMONCONTROLSEX icx; ZeroMemory(&icx,sizeof(icx));
  icx.dwSize=sizeof(icx);
  icx.dwICC=ICC_UPDOWN_CLASS;
  InitCommonControlsEx(&icx);
  //
  WNDCLASS wc; ZeroMemory(&wc,sizeof(wc));
  wc.hInstance=hInstance;
  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
  wc.lpszClassName=_T("ScrMonitor");
  wc.lpfnWndProc=MonitorWndProc;
  RegisterClass(&wc);
  //
  wc.lpfnWndProc=SaverWndProc;
  wc.cbWndExtra=8;
  wc.lpszClassName=_T("ScrClass");
  RegisterClass(&wc);
  //
  if (ScrMode==smConfig) DoConfig(hwnd);
  else if (ScrMode==smSaver || ScrMode==smPreview) DoSaver(hwnd,fakemulti);
  //
  return 0;
}
